// C source file with the function definitions to handle UTF8 characters

// check_utf8_int() function
byte check_utf8_int(utf8_int ch)
{
  // check if it is inside the range
  if (ch > MAX_UTF8_INT)
    return 0;
  
  // special case
  if (ch <= MAX_UTF8_CHAR_1BYTE)
    return 1;
  else if (ch <= MAX_UTF8_CHAR_2BYTE)
    return 2;
  else if (ch <= MAX_UTF8_CHAR_3BYTE)
    return 3;
  else
    return 4;
}

// check_enc_utf8() function
byte check_enc_utf8(void * src, umax ssize)
{
  // check src and src_size
  if (src == NULL || ssize == 0)
    return 0;
  
  // variable to return
  byte ch_size = 0;
  byte * ptr = src;
  
  // check the starting bits of src
  if ((utf8_byte_mask_1 & ptr[0]) == 0) // 1 byte
    ch_size = 1;
  else if ((utf8_byte_mask_3 & ptr[0]) == utf8_byte_mask_2) // 2 bytes
    ch_size = 2;
  else if ((utf8_byte_mask_4 & ptr[0]) == utf8_byte_mask_3) // 3 bytes
    ch_size = 3;
  else if ((utf8_byte_mask_5 & ptr[0]) == utf8_byte_mask_4) // 4 bytes
    ch_size = 4;
  else
    return 0;
  
  // out of bounds UTF8 character
  if (ptr + ch_size > (byte *) src + ssize)
    return 0;
  
  // check each byte's leftmost 2 bits
  for (byte i = 1; i < ch_size; i++)
    // inconsistent UTF8 character construction
    if ((utf8_byte_mask_2 & ptr[i]) != utf8_byte_mask_1) 
      return 0;
  
  // if ch_size != 0 then src constains a valid UTF8 character
  return ch_size;
}

// get_utf8_int() function
utf8_int get_utf8_int(void * src, umax ssize)
{  
  // check if the char is valid
  byte ch_size = check_enc_utf8(src, ssize);
  if (ch_size == 0)
    return INV_UTF8_INT;
  
  // extract the character integer and put it in ch 
  // special case: the character is in 1 byte long
  if (ch_size == 1)
    return (utf8_int) ((byte *) src)[0];
  
  // variable to return
  utf8_int ch = 0;
  
  // ch is 2, 3 or 4 bytes long
  byte * tmp1 = allocate_memory(1, ch_size);
  byte * tmp2 = allocate_memory(1, ch_size);
  cp_mem_bytes(src, ch_size, tmp1); 
  cp_mem_bytes(tmp1, ch_size, tmp2); 
  
  // ptr to write to
  byte * ptr = (byte *) &ch;
  
  // rightmost byte is the first one
  // 2 bytes
  if (ch_size == 2)
  {    
    // first byte                                               // ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = tmp1[1] & (~ utf8_byte_mask_2); // ... 00OOOOOO (ptr)
    
    // second byte                                 // XXXOOOOO XXXXXXXX (tmp1)
    tmp1[0] = tmp1[0] & (~ utf8_byte_mask_3);      // 000OOOOO XXXXXXXX (tmp1)
    tmp1[1] = 0;                                   // 000OOOOO 00000000 (tmp1)
    arr_n_bitshift(tmp1, 2, 1, BITSHIFT_RIGHT, 2); // 00000OOO OO000000 (tmp1)
    ptr[sizeof(utf8_int) - 2] += tmp1[0];          // 00000OOO 00OOOOOO (ptr)
    ptr[sizeof(utf8_int) - 1] += tmp1[1];          // 00000OOO OOOOOOOO (ptr)
  }
  // 3 bytes
  else if (ch_size == 3)
  {    
    // first byte                                                // ... ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = tmp1[2] & (~ utf8_byte_mask_2);  // ... ... 00OOOOOO (ptr)
    
    // second byte                                      // ... XXOOOOOO XXXXXXXX (tmp1)
    tmp1[1] = tmp1[1] & (~ utf8_byte_mask_2);           // ... 00OOOOOO XXXXXXXX (tmp1)
    tmp1[2] = 0;                                        // ... 00OOOOOO 00000000 (tmp1)
    arr_n_bitshift(tmp1 + 1, 2, 1, BITSHIFT_RIGHT, 2);  // ... 0000OOOO OO000000 (tmp1)
    ptr[sizeof(utf8_int) - 2] += tmp1[1];               // ... 0000OOOO 00OOOOOO (ptr)
    ptr[sizeof(utf8_int) - 1] += tmp1[2];               // ... 0000OOOO OOOOOOOO (ptr)
    
    // third byte                         // XXXXOOOO XXXXXXXX XXXXXXXX (tmp2)
    tmp2[0] = tmp2[0] << 4;               // OOOO0000 XXXXXXXX XXXXXXXX (tmp2)
    ptr[sizeof(utf8_int) - 2] += tmp2[0]; // 00000000 OOOOOOOO OOOOOOOO (ptr)
  }
  // 4 bytes
  else if (ch_size == 4)
  {    
    // first byte                                               // ... ... ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = tmp1[3] & (~ utf8_byte_mask_2); // ... ... ... 00OOOOOO (ptr)
    
    // second byte                                      // ... ... XXOOOOOO XXXXXXXX (tmp1)
    tmp1[2] = tmp1[2] & (~ utf8_byte_mask_2);           // ... ... 00OOOOOO XXXXXXXX (tmp1)
    tmp1[3] = 0;                                        // ... ... 00OOOOOO 00000000 (tmp1)
    arr_n_bitshift(tmp1 + 2, 2, 1, BITSHIFT_RIGHT, 2);  // ... ... 0000OOOO OO000000 (tmp1)
    ptr[sizeof(utf8_int) - 2] += tmp1[2];               // ... ... 0000OOOO 00OOOOOO (ptr)
    ptr[sizeof(utf8_int) - 1] += tmp1[3];               // ... ... 0000OOOO OOOOOOOO (ptr)
    
    // third byte
    cp_mem_bytes(tmp2, ch_size, tmp1);                  // ... XXOOOOOO XXXXXXXX XXXXXXXX (tmp1)
    tmp1[1] = tmp1[1] & (~ utf8_byte_mask_2);           // ... 00OOOOOO XXXXXXXX XXXXXXXX (tmp1)
    tmp1[2] = 0;                                        // ... 00OOOOOO 00000000 XXXXXXXX (tmp1)
    arr_n_bitshift(tmp1 + 1, 2, 1, BITSHIFT_RIGHT, 4);  // ... 000000OO OOOO0000 XXXXXXXX (tmp1)
    ptr[sizeof(utf8_int) - 3] += tmp1[1];               // ... 000000OO 0000OOOO OOOOOOOO (ptr)
    ptr[sizeof(utf8_int) - 2] += tmp1[2];               // ... 000000OO OOOOOOOO OOOOOOOO (ptr)
    
    // fourth byte                            // XXXXXOOO XXXXXXXX XXXXXXXX XXXXXXXX (tmp2)
    tmp2[0] = tmp2[0] & (~ utf8_byte_mask_5); // 00000OOO XXXXXXXX XXXXXXXX XXXXXXXX (tmp2)
    tmp2[0] = tmp2[0] << 2;                   // 000OOO00 XXXXXXXX XXXXXXXX XXXXXXXX (tmp2)
    ptr[sizeof(utf8_int) - 3] += tmp2[0];     // 00000000 000OOOOO OOOOOOOO OOOOOOOO (ptr)
  }
  
  // free memory used
  free_memory(tmp1);
  free_memory(tmp2);
  
  // fix the byte order in ch so it is read
  // as the correct integer by the system
  if (SYS_ENDIAN == LITTLE)
    reverse_array(&ch, sizeof(utf8_int), 1);
  
  // return ch
  return ch;
}

// write_enc_utf8() function
void * write_enc_utf8(utf8_int ch, void * dest, umax dsize)
{
  // check if the char is valid
  byte ch_size = check_utf8_int(ch);
  if (dsize < ch_size || dest == NULL || dsize == 0)
    return NULL;
  
  // special case, integer is ASCII
  if (ch_size == 1) /* 0OOOOOOO */ {
    ((byte *) dest)[0] = (byte) ch;
    return (byte *) dest + 1;
  }    
  
  // varible to store the encoded representation
  utf8_int tmp = 0;  
  // extract the integer from ch and apply the operations
  // to convert it to its memory representation  
  byte * tmp1 = allocate_memory(1, ch_size);
  byte * tmp2 = allocate_memory(1, ch_size);
  if (SYS_ENDIAN == BIG)
    cp_mem_bytes((byte *) &ch + sizeof(utf8_int) - ch_size, ch_size, tmp1);
  else if (SYS_ENDIAN == LITTLE) {
    cp_mem_bytes(&ch, ch_size, tmp1);
    reverse_array(tmp1, ch_size, 1);
  }
  cp_mem_bytes(tmp1, ch_size, tmp2);
  
  // ptr to write to
  byte * ptr = (byte *) &tmp;
  
  // rightmost byte is the first one
  // 2 bytes
  if (ch_size == 2)
  {    
    // first byte                                                                     // ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = (tmp1[1] & (~ utf8_byte_mask_2)) + utf8_byte_mask_1;  // ... 10OOOOOO (ptr)       
    // second byte                                          // 00000OOO OOXXXXXX (tmp1)
    arr_n_bitshift(tmp1, 2, 1, BITSHIFT_LEFT, 2);           // 000OOOOO XXXXXX00 (tmp1)
    ptr[sizeof(utf8_int) - 2] = tmp1[0] + utf8_byte_mask_2; // 110OOOOO 10OOOOOO (ptr)
  }
  // 3 bytes
  else if (ch_size == 3)
  {
    // first byte                                                 // ... ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = (tmp1[2] & (~ utf8_byte_mask_2)) 
                                + utf8_byte_mask_1;               // ... ... 10OOOOOO (ptr)    
    // second byte                                                // ... XXXXOOOO OOXXXXXX (tmp1)
    arr_n_bitshift(tmp1 + 1, 2, 1, BITSHIFT_LEFT, 2);             // ... XXOOOOOO XXXXXX00 (tmp1)
    ptr[sizeof(utf8_int) - 2] = (tmp1[1] & (~ utf8_byte_mask_2))
                                + utf8_byte_mask_1;               // ... 10OOOOOO 10OOOOOO (ptr)    
    // third byte                                            // 00000000 OOOOXXXX XXXXXXXX (tmp2)
    tmp2[1] = tmp2[1] >> 4;                                  // 00000000 0000OOOO XXXXXXXX (tmp2)
    ptr[sizeof(utf8_int) - 3] = tmp2[1] + utf8_byte_mask_3;  // 1110OOOO 10OOOOOO 10OOOOOO (ptr)
  }
  // 4 bytes
  else if (ch_size == 4)
  {
    // first byte                                                 // ... ... ... XXOOOOOO (tmp1)
    ptr[sizeof(utf8_int) - 1] = (tmp1[3] & (~ utf8_byte_mask_2))
                                + utf8_byte_mask_1;               // ... ... ... 10OOOOOO (ptr)    
    // second byte                                                // ... ... XXXXOOOO OOXXXXXX (tmp1)
    arr_n_bitshift(tmp1 + 2, 2, 1, BITSHIFT_LEFT, 2);             // ... ... XXOOOOOO XXXXXX00 (tmp1)
    ptr[sizeof(utf8_int) - 2] = (tmp1[2] & (~ utf8_byte_mask_2))
                                + utf8_byte_mask_1;               // ... ... 10OOOOOO 10OOOOOO (ptr)    
    // third byte 
    cp_mem_bytes(tmp2, ch_size, tmp1);                            // ... XXXXXXOO OOOOXXXX XXXXXXXX (tmp1)
    arr_n_bitshift(tmp1 + 1, 2, 1, BITSHIFT_LEFT, 4);             // ... XXOOOOOO XXXX0000 XXXXXXXX (tmp1)
    ptr[sizeof(utf8_int) - 3] = (tmp1[1] & (~ utf8_byte_mask_2))
                                + utf8_byte_mask_1;               // ... 10OOOOOO 10OOOOOO 10OOOOOO (ptr)    
    // fourth byte                                          // 00000000 000OOOXX XXXXXXXX XXXXXXXX (tmp2)
    tmp2[1] = tmp2[1] >> 2;                                 // 00000000 00000OOO XXXXXXXX XXXXXXXX (tmp2)
    ptr[sizeof(utf8_int) - 4] = tmp2[1] + utf8_byte_mask_4; // 11110OOO 10OOOOOO 10OOOOOO 10OOOOOO (ptr)
  }
  
  // free memory used
  free_memory(tmp1);
  free_memory(tmp2);
  
  // write the integer into dest
  cp_mem_bytes(ptr + sizeof(utf8_int) - ch_size, ch_size, dest);
  return (byte *) dest + ch_size;
}

// print_utf8_int() function
byte print_utf8_int(utf8_int ch)
{
  // invalid character
  byte rtn = check_utf8_int(ch);
  if (rtn == 0)
    printf(INV_UTF8_INT_STR);
  else
    print_unicd_int((unicd_int) ch);
  return rtn;
}

// print_enc_utf8() function
byte print_enc_utf8(void * src, umax ssize)
{
  // invalid character
  byte rtn = check_enc_utf8(src, ssize);
  if (rtn == 0)
    printf(INV_ENC_UTF8_STR);
  else // print the encoded bytes
    for (byte i = 0; i < rtn; i++)
      printf("%02X", ((byte *) src + i)[0]);    
  return rtn;
}

// check_utf8_as_unicd() function
byte check_utf8_as_unicd(utf8_int ch)
{
  // all characters on UTF8 are able to be converted to unicode
  return check_utf8_int(ch);
}

// check_unicd_as_utf8() function
bool check_unicd_as_utf8(unicd_int ch)
{
  // check if it is an Unicode character (0x000000 - 0x1FFFFF)
  if (ch >= MIN_UNICD_INT && ch <= MAX_UNICD_INT)
    return 1;
  return 0;
}

// get_utf8_as_unicd() function
unicd_int get_utf8_as_unicd(utf8_int ch)
{
  // check first, then return
  if (check_utf8_as_unicd(ch) == 0)
    return INV_UNICD_INT;
  return (unicd_int) ch;
}

// get_unicd_as_utf8() function
utf8_int get_unicd_as_utf8(unicd_int ch)
{
  // check first, then return
  if (check_unicd_as_utf8(ch) == 0)
    return INV_UTF8_INT;
  return (utf8_int) ch;
}
